<template>
  <view ref="dragRef" class="l-drag l-class" :style="[areaStyles]" @touchstart="setDisabled">
    <movable-area v-if="isReset" class="l-drag__inner" :style="[innerStyles]">
      <slot></slot>
      <movable-view
        v-if="isDrag && props.ghost"
        key="l-drag-clone"
        class="l-drag__ghost"
        :animation="true"
        :style="[viewStyles]"
        direction="all"
        :x="ghostEl.x"
        :y="ghostEl.y"
      >
        <slot name="ghost"></slot>
      </movable-view>
      <movable-view
        v-if="props.before"
        class="l-drag__before"
        disabled
        :animation="false"
        :style="[viewStyles]"
        :x="beforeEl.x"
        :y="beforeEl.y"
      >
        <slot name="before"></slot>
      </movable-view>
      <movable-view
        v-for="(item, oindex) in cloneList"
        :key="item.id"
        direction="all"
        :data-oindex="oindex"
        :style="[viewStyles]"
        class="l-drag__view"
        :class="[{ 'l-is-active': oindex == active, 'l-is-hidden': !item.show }, item.class]"
        :x="item.x"
        :y="item.y"
        :friction="friction"
        :damping="damping"
        :animation="animation"
        :disabled="isDisabled || props.disabled"
        @touchstart="touchStart"
        @change="touchMove"
        @touchend="touchEnd"
        @touchcancel="touchEnd"
        @longpress="setDisabled"
      >
        <!-- <view v-if="props.remove" class="l-drag__remove" :style="removeStyle" data-remove="true">
					<slot name="remove" :oindex="oindex" data-remove="true" />
				</view> -->
        <!-- <view v-if="props.handle" class="l-drag__handle" :style="handleStyle" data-handle="true">
					<slot name="handle" :oindex="oindex" :active="!isDisabled && !isDisabled && oindex == active" />
				</view> -->
        <slot
          name="grid"
          :oindex="oindex"
          :index="item.index"
          :oldindex="item.oldindex"
          :content="item.content"
          :active="!isDisabled && !isDisabled && oindex == active"
        />
        <view v-if="!(isDisabled || props.disabled) && props.longpress" class="mask"></view>
      </movable-view>

      <movable-view
        v-if="props.after"
        class="l-drag__after"
        disabled
        :animation="true"
        direction="all"
        :style="[viewStyles]"
        :x="afterEl.x"
        :y="afterEl.y"
      >
        <slot name="after"></slot>
      </movable-view>
    </movable-area>
  </view>
</template>
<script lang="ts">
// 组件地址：https://ext.dcloud.net.cn/plugin?id=11474
// @ts-nocheck
import DragProps from './props';
import type { Grid, GridRect, Position } from './type';
import {
  computed,
  defineComponent,
  getCurrentInstance,
  nextTick,
  onMounted,
  onUnmounted,
  reactive,
  ref,
  triggerRef,
  watch,
} from './vue';
// #ifdef APP-NVUE
const dom = weex.requireModule('dom');
// #endif

export default defineComponent({
  name: 'LDrag',
  externalClasses: ['l-class'],
  options: {
    addGlobalClass: true,
    virtualHost: true,
  },
  props: DragProps,
  emits: ['change'],
  setup(props, { emit, expose }) {
    // #ifdef APP-NVUE
    const dragRef = ref(null);
    // #endif
    const app = getCurrentInstance();
    const isDrag = ref(false);
    const isInit = ref(false);
    const isReset = ref(true);
    const colmunId = ref(-1);
    /** 选中项原始下标 */
    const active = ref(-1);
    const maxIndex = ref(-1);
    const animation = ref(true);
    const isDisabled = ref(props.handle || props.longpress ? true : false);

    const dragEl = reactive({
      content: null,
      /** 当前视图下标*/
      index: 0,
      /** 旧视图下标 */
      oldindex: -1,
      /** 上次原始下标 */
      lastindex: -1,
    });

    const ghostEl = reactive({
      content: null,
      x: 0,
      y: 0,
    });
    const beforeEl = reactive({
      x: 0,
      y: 0,
    });
    const afterEl = reactive({
      x: 0,
      y: 0,
    });

    let gridRects = []; //ref<GridRect[]>([])
    const areaWidth = ref(0);
    const cloneList = ref<Grid[]>([]);
    // 删除项时可能会减少行数影响到删除过渡动画，故增加此值在删除时保持高度不变，等动画完成后再归零
    const leaveRow = ref(0);
    const extra = computed(() => (props.before ? 1 : 0) + (props.after ? 1 : 0));
    const rows = computed(() =>
      Math.ceil(
        ((isInit.value ? cloneList.value.length : props.list.length) + extra.value) / props.column,
      ),
    );
    const gridHeight = computed(() =>
      props.aspectRatio
        ? girdWidth.value / props.aspectRatio
        : /rpx$/.test(`${props.gridHeight}`)
        ? uni.upx2px(parseInt(`${props.gridHeight}`))
        : parseInt(`${props.gridHeight}`),
    );
    const girdWidth = computed(() => areaWidth.value / props.column);
    const viewStyles = computed(() => ({
      width: girdWidth.value + 'px',
      height: gridHeight.value + 'px',
    }));
    const areaStyles = computed(() => ({
      height: (rows.value + leaveRow.value) * gridHeight.value + 'px',
    }));
    const innerStyles = computed(() => ({
      // #ifdef APP-NVUE
      width: areaWidth.value + 'px',
      // #endif
      height: (rows.value + props.extraRow + leaveRow.value) * gridHeight.value + 'px',
    }));

    const sleep = (cb: Function, time = 1000 / 60) => setTimeout(cb, time);
    const createGrid = (content: any, position?: Position | null): Grid => {
      colmunId.value++;
      maxIndex.value++;
      const index = maxIndex.value;
      const colmun = gridRects[index];

      let x = 0;
      let y = 0;
      if (colmun) {
        if (props.after) {
          let nxet = gridRects[index + 1];
          if (!nxet) {
            nxet = createGridRect(gridRects.length + (props.before ? 1 : 0));
            gridRects.push(nxet);
          }
          setReset(() => setAfter(nxet));
        } else {
          setReset();
        }
        x = colmun.x;
        y = colmun.y;
      } else {
        const nxet = createGridRect(gridRects.length + (props.before ? 1 : 0));
        gridRects.push(nxet);
        setReset();
        x = nxet.x;
        y = nxet.y;
      }
      if (position) {
        x = position.x;
        y = position.y;
      }
      return {
        id: `l-drag-item-${colmunId.value}`,
        index,
        oldindex: index,
        content,
        x,
        y,
        class: '',
        show: true,
      };
    };
    const setReset = (cb?: any) => {
      // const newRow = (cloneList.value.length + extra.value) % (props.column)
      if (isInit.value) {
        cb && sleep(cb);
      }
    };
    const setAfter = ({ x, y } = { x: 0, y: 0 }) => {
      if (props.after) {
        afterEl.x = x;
        afterEl.y = y;
      }
    };
    const setDisabled = (e: any, flag) => {
      // e?.preventDefault()
      const type = `${e.type}`.toLowerCase();
      const { handle = props.touchHandle } = e.target.dataset;
      if (props.handle && !handle) {
        isDisabled.value = true;
      } else if (props.handle && handle && !props.longpress) {
        isDisabled.value = flag;
      } else if (props.handle && handle && props.longpress && type.includes('longpress')) {
        isDisabled.value = false;
      } else if (props.longpress && type.includes('longpress') && !props.handle) {
        isDisabled.value = false;
      }
      if (type.includes('touchend') && props.longpress) {
        isDisabled.value = true;
      }
    };
    const createGridRect = (i: number, last?: GridRect): GridRect => {
      let { row } = last || gridRects[gridRects.length - 1] || { row: 0 };
      const col = i % props.column;
      const grid = (row: number, x: number, y: number): GridRect => {
        return { row, x, y, x1: x + girdWidth.value, y1: y + gridHeight.value };
      };
      if (col == 0 && i != 0) {
        row++;
      }
      return grid(row, col * girdWidth.value, row * gridHeight.value);
    };
    const createGridRects = () => {
      let rects: GridRect[] = [];
      const length = rows.value * props.column + extra.value;
      gridRects = [];
      for (var i = 0; i < length; i++) {
        const item = createGridRect(i, rects[rects.length - 1]);
        rects.push(item);
      }
      if (props.before) {
        const { x, y } = rects.shift();
        beforeEl.x = x;
        beforeEl.y = y;
      }
      setAfter(rects[props.list.length]);
      gridRects = rects as GridRect[];
    };
    const updateList = (v: any[]) => {
      cloneList.value = v.map(content => createGrid(content));
    };

    const touchStart = (e: any) => {
      if (e.target.dataset.remove) return;
      // 选中项原始下标
      const { oindex } = e.currentTarget?.dataset || e.target?.dataset || {};
      if (typeof oindex !== 'number') return;
      const target = cloneList.value[oindex];
      isDrag.value = true;
      // 选中项原始下标
      active.value = oindex;
      // 选中项的当前下标
      dragEl.index = dragEl.oldindex = target.index;
      ghostEl.x = target.x || 0;
      ghostEl.y = target.y || 0;
      dragEl.content = ghostEl.content = target.content;
    };

    const touchEnd = (e: any) => {
      setTimeout(() => {
        if (e.target.dataset.remove || active.value == -1) return;
        setDisabled(e, true);
        isDrag.value = false;
        const isEmit = dragEl.index !== dragEl.oldindex && dragEl.oldindex > -1; // active.value !== dragEl.index
        dragEl.lastindex = active.value;
        dragEl.oldindex = active.value = -1;
        const last = cloneList.value[dragEl.lastindex];
        const position = gridRects[dragEl.index];
        nextTick(() => {
          last.x = position.x + 0.001;
          last.y = position.y + 0.001;
          sleep(() => {
            last.x = position.x;
            last.y = position.y;
            isEmit && emitting();
          });
        });
      }, 80);
    };
    const emitting = () => {
      const clone = [...cloneList.value].sort((a, b) => a.index - b.index); //.map(item => ref(item.content))
      emit('change', clone);
    };

    const touchMove = (e: any) => {
      if (!isDrag.value) return;
      // #ifndef APP-NVUE
      let { oindex } = e.currentTarget.dataset;
      // #endif
      // #ifdef APP-NVUE
      oindex = e.currentTarget.dataset['-oindex'];
      // #endif
      if (oindex != active.value) return;
      const { x, y } = e.detail;
      const centerX = x + girdWidth.value / 2;
      const centerY = y + gridHeight.value / 2;
      for (let i = 0; i < cloneList.value.length; i++) {
        const item = gridRects[i];
        if (centerX > item.x && centerX < item.x1 && centerY > item.y && centerY < item.y1) {
          ghostEl.x = item.x;
          ghostEl.y = item.y;
          if (dragEl.index != i) {
            _move(active.value, i);
          }
          break;
        }
      }
    };
    const getDragEl = (oindex: number) => {
      if (isDrag.value) {
        return dragEl;
      }
      return cloneList.value[oindex];
    };

    /**
     * 把原始数据中排序为index的项 移动到 toIndex
     * @param oindex 原始数据的下标
     * @param toIndex 视图中的下标
     * @param position 指定坐标
     */
    const _move = (oindex: number, toIndex: number, position?: Position | null, emit = true) => {
      const length = cloneList.value.length - 1;
      if (toIndex > length || toIndex < 0) return;
      // 获取oIdnex在视图中的项目
      const dragEl = getDragEl(oindex);
      let speed = 0;
      let start = dragEl.index;
      // 比较开始index和终点index，设置方向
      if (start < toIndex) {
        speed = 1;
      }
      if (start > toIndex) {
        speed = -1;
      }
      if (!speed) return;
      // 距离
      let distance = start - toIndex;
      // 找到区间所有的项
      while (distance) {
        distance += speed;
        // 目标
        const target = isDrag.value ? (dragEl.index += speed) : (start += speed);
        let targetOindex = cloneList.value.findIndex(
          item => item.index == target && item.content != dragEl.content,
        );
        if (targetOindex == oindex) return;
        if (targetOindex < 0) {
          targetOindex = cloneList.value.length - 1;
        }
        let targetEl = cloneList.value[targetOindex];
        if (!targetEl) return;
        // 上一个index
        const lastIndex = target - speed;
        const activeEl = cloneList.value[oindex];
        const rect = gridRects[lastIndex];
        targetEl.x = rect.x;
        targetEl.y = rect.y;
        targetEl.oldindex = targetEl.index;
        targetEl.index = lastIndex;
        activeEl.oldindex = activeEl.index; //oIndex
        activeEl.index = toIndex;
        // 到达终点，如果是拖拽则不处理
        if (!distance && !isDrag.value) {
          const rect = gridRects[toIndex];
          const { x, y } = position || rect;
          activeEl.x = dragEl.x = x;
          activeEl.y = dragEl.y = y;
          // triggerRef(cloneList)
          if (emit) {
            emitting();
          }
        }
      }
    };
    /**
     * 为区分是主动调用还是内部方法
     */
    const move = (oindex: number, toIndex: number) => {
      active.value = -1;
      isDrag.value = false;
      _move(oindex, toIndex);
    };
    // 临时处理 待有空再完善
    const REMOVE_TIME = 400;
    let removeTimer = null;
    const remove = (oindex: number) => {
      active.value = -1;
      isDrag.value = false;

      clearTimeout(removeTimer);
      const item = cloneList.value[oindex];
      if (props.disabled || !item) return;
      item.show = false;
      const after = cloneList.value.length - 1;
      _move(oindex, after, item, false);
      setAfter(gridRects[after]);
      maxIndex.value--;
      const _remove = (_index = oindex) => {
        // 小程序 删除会闪一下 所以先关闭动画再开启
        // animation.value = false
        const row = Math.ceil((cloneList.value.length - 1 + extra.value) / props.column);
        if (row < rows.value) {
          leaveRow.value = rows.value - row;
        }
        cloneList.value.splice(_index, 1)[0];
        emitting();
        removeTimer = setTimeout(() => {
          leaveRow.value = 0;
        }, REMOVE_TIME);
      };
      _remove();
    };
    const push = (...args: any) => {
      if (props.disabled) return;
      if (Array.isArray(args)) {
        Promise.all(args.map(async item => await add(item, true))).then(emitting);
      }
    };
    const add = (content: any, after: boolean) => {
      return new Promise(resolve => {
        const item = createGrid(content, after ? null : { x: -100, y: 0 });
        item.class = 'l-drag-enter';
        cloneList.value.push(item);
        const length = cloneList.value.length - 1;
        nextTick(() => {
          sleep(() => {
            item.class = 'l-drag-leave';
            _move(length, after ? length : 0, null, false);
            triggerRef(cloneList);
            resolve(true);
          });
        });
      });
    };
    const unshift = (...args: any) => {
      if (props.disabled) return;
      if (Array.isArray(args)) {
        Promise.all(args.map(async item => await add(item))).then(emitting);
      }
    };

    // 暂时先简单处理，待有空再完善
    const shift = () => {
      if (!cloneList.value.length) return;
      remove(cloneList.value.findIndex(item => item.index == 0) || 0);
    };
    const pop = () => {
      const length = cloneList.value.length - 1;
      if (length < 0) return;
      remove(cloneList.value.findIndex(item => item.index == length) || length);
    };
    // const splice = (start, count, ...context) => {
    // 	// 暂未实现
    // }
    const clear = () => {
      isInit.value = isDrag.value = false;
      maxIndex.value = colmunId.value = active.value = -1;
      cloneList.value = [];
      gridRects = [];
    };
    const init = () => {
      clear();
      createGridRects();
      nextTick(() => {
        updateList(props.list);
        isInit.value = true;
      });
    };
    let count = 0;
    const getRect = () => {
      count++;
      // #ifndef APP-NVUE
      uni
        .createSelectorQuery()
        .in(app.proxy)
        .select('.l-drag')
        .boundingClientRect((res: UniNamespace.NodeInfo) => {
          if (res) {
            areaWidth.value = res.width || 0;
            // 小程序居然无法响应式？
            init();
          }
        })
        .exec();
      // #endif
      // #ifdef APP-NVUE
      sleep(() => {
        nextTick(() => {
          dom.getComponentRect(dragRef.value, res => {
            if (!res.size.width && count < 5) {
              getRect();
            } else {
              areaWidth.value = res.size.width || 0;
              init();
            }
          });
        });
      });
      // #endif
    };
    onMounted(getRect);
    onUnmounted(clear);
    watch(() => props.list, init);

    // #ifdef VUE3
    expose({
      remove,
      // add,
      move,
      push,
      unshift,
      shift,
      pop,
    });
    // #endif
    return {
      // #ifdef APP-NVUE
      dragRef,
      // #endif
      cloneList,

      areaStyles,
      innerStyles,
      viewStyles,

      setDisabled,
      isDisabled,
      isReset,
      isDrag,

      active,
      animation,

      afterEl,
      ghostEl,
      beforeEl,

      touchStart,
      touchMove,
      touchEnd,

      remove,
      // add,
      move,
      push,
      unshift,
      // shift,
      // pop,
      props,
      // isDelete: props.delete,
      // ...toRefs(props)
    };
  },
});
</script>
<style lang="scss">
@import './index';
</style>
